##
# Author: Samuel Abels <http://debain.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Library General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
##
import os, pango, gobject, gtk, util, gtkspell, locale
from gettext                    import gettext as _
from config                     import c as cfg
from Workflow                   import Workflow
from WorkflowSelector           import WorkflowSelector
from SpiffGtkWidgets.TextEditor import TextEditor, Annotation
from SpiffGtkWidgets.color      import to_gdk

class WorkflowEditor(gobject.GObject):
    def __init__(self, controller, workflow):
        gobject.GObject.__init__(self)
        self.controller  = controller
        self.workflow    = workflow
        gladefile        = cfg['APP_SYSNAME'] + '.glade'
        dialogname       = 'dialog_edit_workflow'
        self.xml         = util.get_glade_xml(gladefile, dialogname)
        self.window      = self.xml.get_widget(dialogname)
        self.scroll      = self.xml.get_widget('scrolledwindow_content')
        self.undo_button = self.xml.get_widget('button_undo')
        self.redo_button = self.xml.get_widget('button_redo')
        self.editor      = TextEditor()
        self.text_buffer = self.editor.get_buffer()
        self.lock_format = False

        # Style code in the text editor.
        color = self.editor.get_style().bg[gtk.STATE_NORMAL]
        style = {'background-full-height': True,
                 'background-gdk':         color,
                 'family':                 'Monospace',
                 'left-margin':            12}
        self.text_buffer.create_tag('code', **style)

        # Add a style for subtitles.
        self.text_buffer.create_tag('title',
                                    scale  = pango.SCALE_LARGE,
                                    weight = pango.WEIGHT_BOLD)

        # Compose all widgets.
        self.editor.set_accepts_tab(False)
        self.editor.set_handle_links(False)
        self.editor.set_border_width(6)
        self.editor.connect('annotation-focus-out-event',
                            self._on_annotation_focus_out_event)
        self.expose_event = self.editor.connect('expose-event',
                                                self._editor_expose_event_cb)
        self.text_buffer.connect('changed', self._field_changed_cb)
        self.text_buffer.connect('mark-set', self._mark_set_cb)
        self.text_buffer.connect('undo-stack-changed',
                                 self._on_buffer_undo_stack_changed)
        self.scroll.add(self.editor)
        self.xml.signal_autoconnect(self)
        self.window.connect('response', self._on_window_response)
        self.window.show_all()

        # Work around a Glade bug (Glade incorrectly ignores the "secondary"
        # property).
        button_bar = self.xml.get_widget('buttonbox')
        button     = self.xml.get_widget('button_delete')
        button_bar.set_child_secondary(button, True)

        # Enable spell checking.
        lang = locale.getlocale()[0]
        if lang is None:
            print "WARNING: Spell checking diabled because language is not set."
            self.gtkspell = None
        else:
            self.gtkspell = gtkspell.Spell(self.editor)
            self.gtkspell.set_language(lang)

        # Init the content.
        self._update()


    def _editor_expose_event_cb(self, editor, event):
        self.editor.disconnect(self.expose_event)
        style = self.window.get_style()
        self.editor.modify_bg(gtk.STATE_NORMAL, style.light[gtk.STATE_NORMAL])


    def _update(self):
        # Fill the entry fields.
        widget = self.xml.get_widget('entry_workflow')
        widget.set_text(self.workflow.get_name())
        widget = self.xml.get_widget('entry_summary')
        widget.set_text(self.workflow.get_summary())

        # Fill the text area.
        content = self.workflow.get_content()
        if content:
            self.text_buffer.restore(content)

        # Add annotations.
        self.text_buffer.flush_undo_stack()
        self._field_changed_cb()


    def set_workflow(self, workflow):
        self.workflow = workflow
        self._update()


    def hide(self):
        self.window.hide()


    def _duplicate_name_dialog(self, name):
        title    = _('Workflow named "%s" already exists!') % name
        title    = '<span weight="bold" size="larger">%s</span>' % title
        body     = _('A workflow with the entered name already exists.\n'
                   + 'Please enter a different name.')
        dialog = gtk.MessageDialog(type    = gtk.MESSAGE_WARNING,
                                   buttons = gtk.BUTTONS_CLOSE)
        dialog.set_markup(title + '\n\n' + body)
        dialog.show_all()
        dialog.run()
        dialog.destroy()


    def _confirm_delete_dialog(self, name):
        title    = _('Really delete "%s"?') % name
        title    = '<span weight="bold" size="larger">%s</span>' % title
        body     = _('Are you sure that you want to delete the workflow?\n'
                   + 'It will be hidden from the workflow list, but you'
                   + ' may still re-activate it later.')
        label    = _('Delete Workflow')
        cancel   = gtk.Button(stock = 'gtk-cancel')
        discard  = gtk.Button(stock = 'gtk-delete')
        discard.get_child().get_child().get_children()[1].set_text(label)
        dialog   = gtk.MessageDialog(type    = gtk.MESSAGE_WARNING,
                                     buttons = gtk.BUTTONS_NONE)
        dialog.set_markup(title + '\n\n' + body)
        dialog.add_action_widget(cancel,  0)
        dialog.add_action_widget(discard, 1)
        dialog.show_all()
        response = dialog.run()
        dialog.destroy()
        if response == 0:
            return False
        return True


    def hide(self):
        self.window.hide()


    def destroy(self):
        self.window.destroy()


    ################################################################
    # Callbacks.
    ################################################################
    def _on_window_response(self, window, response):
        # Cancel pressed?
        if response == -1:
            return self.emit('cancel')

        # Delete pressed?
        name = self.workflow.get_name()
        if response == -2:
            if not self._confirm_delete_dialog(name):
                return
            self.controller.workflow_db.delete_workflow(self.workflow)
            return self.emit('save', self.workflow)

        # Window closed by the window manager?
        if response != 0:
            return self.emit('cancel')

        # Get data from entry fields.
        name    = self.xml.get_widget('entry_workflow').get_text()
        summary = self.xml.get_widget('entry_summary').get_text()
        if name != self.workflow.get_name() \
          and self.controller.workflow_db.workflow_exists(name):
            self._duplicate_name_dialog(name)
            return
        self.workflow.set_name(name)
        self.workflow.set_summary(summary)

        # Disable gtkspell to prevent it's tags from being included in the
        # dump.
        if self.gtkspell:
            self.gtkspell.detach()

        # Get data from the text area.
        buffer  = self.editor.get_buffer()
        bounds  = buffer.get_bounds()
        content = buffer.dump()
        self.workflow.set_content(content)

        # Save.
        self.controller.workflow_db.save_workflow(self.workflow)
        self.emit('save', self.workflow)


    def _mark_set_cb(self, *args):
        if self.lock_format:
            return
        self.lock_format = True
        widget = self.xml.get_widget('button_bold')
        widget.set_active(self.text_buffer.selection_has_tag('bold'))
        widget = self.xml.get_widget('button_italic')
        widget.set_active(self.text_buffer.selection_has_tag('italic'))
        widget = self.xml.get_widget('button_underline')
        widget.set_active(self.text_buffer.selection_has_tag('underline'))
        widget = self.xml.get_widget('button_indent')
        widget.set_active(self.text_buffer.selection_has_tag('code'))
        widget = self.xml.get_widget('button_title')
        widget.set_active(self.text_buffer.selection_has_tag('title'))
        self.lock_format = False


    def _field_changed_cb(self, *args):
        widget  = self.xml.get_widget('entry_workflow')
        name    = widget.get_text()
        widget  = self.xml.get_widget('entry_summary')
        summary = widget.get_text()
        chars   = self.editor.get_buffer().get_char_count()
        widget  = self.xml.get_widget('button_save')
        widget.set_sensitive(name != '' and summary != '' and chars > 0)
        self.window.set_title(name or _('Edit Workflow'))


    def _button_title_clicked_cb(self, button):
        if self.lock_format:
            return
        self.text_buffer.toggle_selection_tag('title')


    def _button_bold_clicked_cb(self, button):
        if self.lock_format:
            return
        self.text_buffer.toggle_selection_tag('bold')


    def _button_italic_clicked_cb(self, button):
        if self.lock_format:
            return
        self.text_buffer.toggle_selection_tag('italic')


    def _button_underline_clicked_cb(self, button):
        if self.lock_format:
            return
        self.text_buffer.toggle_selection_tag('underline')


    def _button_indent_clicked_cb(self, button):
        if self.lock_format:
            return
        self.text_buffer.toggle_selection_tag('code')


    def _button_undo_clicked_cb(self, button):
        self.text_buffer.undo()


    def _button_redo_clicked_cb(self, button):
        self.text_buffer.redo()


    def _button_add_annotation_clicked_cb(self, button):
        # Mark the cursor position in the text buffer.
        buffer     = self.text_buffer
        cursor_pos = buffer.get_property('cursor-position')
        iter       = buffer.get_iter_at_offset(cursor_pos)
        mark_name  = 'annotation%d'
        mark_num   = 1
        while buffer.get_mark(mark_name % mark_num) is not None:
            mark_num += 1
        mark = buffer.create_mark(mark_name % mark_num, iter, iter.is_end())

        # Add an annotation at the mark.
        annotation = Annotation(mark)
        annotation.set_title(os.environ.get('USER', _('Unknown')))
        self.text_buffer.add_annotation(annotation)


    def _mk_link_tag(self, link_dest):
        tag       = 'link%d'
        num       = 1
        tag_table = self.text_buffer.get_tag_table()
        while tag_table.lookup(tag % num) is not None:
            num += 1
        tag = self.text_buffer.create_link_tag(tag % num, link_dest)
        return tag


    def _button_add_link_clicked_cb(self, button):
        selector = WorkflowSelector(self.controller)
        selector.connect('cancel', self._on_link_dialog_cancel)
        selector.connect('select', self._on_link_dialog_select)


    def _on_link_dialog_cancel(self, dialog):
        dialog.hide()


    def _on_link_dialog_select(self, dialog, workflow):
        dialog.hide()
        name       = workflow.get_name()
        uri        = workflow.get_uri()
        tag        = self._mk_link_tag(uri)
        cursor_pos = self.text_buffer.get_property('cursor-position')
        iter       = self.text_buffer.get_iter_at_offset(cursor_pos)
        self.text_buffer.insert_with_tags(iter, name, tag)


    def _on_buffer_undo_stack_changed(self, buffer):
        self.undo_button.set_sensitive(buffer.can_undo())
        self.redo_button.set_sensitive(buffer.can_redo())
        self._mark_set_cb()


    def _on_annotation_focus_out_event(self, editor, annotation):
        if annotation.get_text() == '':
            self.text_buffer.remove_annotation(annotation)

gobject.type_register(WorkflowEditor)

gobject.signal_new('cancel',
                   WorkflowEditor,
                   gobject.SIGNAL_RUN_FIRST,
                   gobject.TYPE_NONE,
                   ())

gobject.signal_new('save',
                   WorkflowEditor,
                   gobject.SIGNAL_RUN_FIRST,
                   gobject.TYPE_NONE,
                   (gobject.TYPE_PYOBJECT,))
